﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.Collections;
using System.Configuration;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text.RegularExpressions;
using System.Threading;
using System.Transactions;
using System.Xml.Linq;
using BlueCurve.Common;
using BlueCurve.Common.Threads;
using BlueCurve.Common.Transaction;
using BlueCurve.WebCrawlerRequest;

namespace WebCrawler
{
    /// <summary>
    /// Class de traitement des réponses reçu
    /// des serveurs web
    /// </summary>
    public class ResponseTreat
    {
        #region 'Configuration'

        /// <summary>
        /// Permets de laisse tourner le threads de traitement
        /// </summary>
        private bool m_IsRun = true;
        /// <summary>
        /// Nombre de threads tourant
        /// </summary>
        private long m_AliveThreads = 1;
        /// <summary>
        /// Regxp pour le nettoyage des noms de fichiers
        /// </summary>
        private Regex m_NonAllowedCharactersInDocumentPath = new Regex(@"\\|/|\:|\*|\?|<|>|\||\""", RegexOptions.CultureInvariant | RegexOptions.Multiline);
        /// <summary>
        /// Chemin ou écrire les fichiers téléchargés
        /// </summary>
        private readonly string m_FileDestination = ConfigurationManager.AppSettings["DownloadedFilesDestination"];
        /// <summary>
        /// Pool de threads
        /// </summary>
        private SimpleLockThreadPool m_ThreadPool = new SimpleLockThreadPool(Environment.ProcessorCount * Convert.ToInt32(ConfigurationManager.AppSettings["ThreadsPerProcessor"]), true);
        /// <summary>
        /// Pile de données à traiter
        /// </summary>
        private Queue m_DatasQueue = new Queue();
        /// <summary>
        /// Objet de vérrouillage
        /// </summary>
        private readonly object m_Threadslock = new object();
        /// <summary>
        /// Ajoute des fichier dans la cache a destination du repository
        /// </summary>
        public delegate void AddFileinfoEventHandler(object sender, AddFileinfo e);
        public event AddFileinfoEventHandler AddFileToRepositoryCache;


        public ResponseTreat()
        {
            if (!Directory.Exists(this.m_FileDestination))
                Directory.CreateDirectory(this.m_FileDestination);
        }

        
        /// <summary>
        /// Permet d'arreter
        /// </summary>
        public bool IsStop
        {
            set { this.m_IsRun = value; }
            get { return this.m_IsRun; }
        }

        /// <summary>
        /// Retourne le nombre de threads en cours
        /// </summary>
        public long AliveThreads
        {
            get { return Interlocked.Read(ref this.m_AliveThreads); }
        }

        #endregion




        #region 'Functions'

        /// <summary>
        /// Fonction de traitement des données téléchargés
        /// </summary>
        public void TreatDownloadedData()
        {
            this.Reload();
            while (this.m_IsRun)
            {
                lock (this.m_DatasQueue.SyncRoot)
                {
                    while(this.m_DatasQueue.Count > 0)
                    {
                        this.m_ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), this.m_DatasQueue.Dequeue());
                    }
                }

                Thread.Sleep(3 * 1000);
            }

            while (this.AliveThreads > 0) { Thread.Sleep(150); }
            if (!this.IsStop)
                this.Save();

            Interlocked.Decrement(ref this.m_AliveThreads);
        }


        /// <summary>
        /// Ajoute un objet dans la collection
        /// </summary>
        /// <param name="data">Object à ajouter</param>
        public void Add(object data)
        {
            lock(this.m_DatasQueue.SyncRoot)
            {
                if( data != null)
                    this.m_DatasQueue.Enqueue(data);
            }
        }
        

        /// <summary>
        /// Callback attaché au pool de threads
        /// </summary>
        /// <param name="data">Données à traiter</param>
        private void DoWork(object data)
        {
            Interlocked.Increment(ref this.m_AliveThreads);
            RequestState rs = (RequestState)data;
            string tempPath = null;
            bool noerror = true;
            try
            {
                // Nettoyage du nom de fichier
                tempPath = Path.GetFullPath(string.Concat(this.m_FileDestination, this.m_NonAllowedCharactersInDocumentPath.Replace(rs.Uri.ToString(), "")));

                if (Directory.Exists(tempPath))
                    return;

                Directory.CreateDirectory(tempPath);

                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TimeSpan(0, 2, 40)))
                {
                    FileGenerationService instanceFile = new FileGenerationService();
                    using (TransactionScope statetx = new TransactionScope(TransactionScopeOption.RequiresNew))
                    {
                        instanceFile.Generate(rs, Path.Combine(tempPath, "requeststate.bin"));
                        statetx.Complete();
                    }
                    using (TransactionScope responsetx = new TransactionScope(TransactionScopeOption.RequiresNew))
                    {
                        string Location = null;
                        if (rs.TypeOffRequest == RequestType.http)
                        {
                            Location = string.IsNullOrEmpty(rs.Headers[HttpResponseHeader.Location]) ? "" : rs.Headers[HttpResponseHeader.Location];
                        }
                        if (rs.TypeOffRequest == RequestType.ftp)
                        {
                            CommonFunctions common = new CommonFunctions();
                            if (!common.NormalizeLink(rs.ResponseUri.ToString()).Equals(rs.Uri.ToString(), StringComparison.InvariantCultureIgnoreCase))
                            {
                                if (!string.IsNullOrEmpty(rs.ResponseUri.ToString()))
                                    Location = rs.ResponseUri.ToString();
                            }
                        }
                        if (rs.TypeOffRequest == RequestType.gopher)
                        {
                            throw new NotImplementedException();
                        }

                        // Création d'un fichier XML qui sera utilisé pour savoir si le fichier à été redirigé
                        // Mais aussi pour la crawlingapplication
                        XElement Xmldef = new XElement("node",
                              new XElement("url",
                                   new XCData(rs.Uri.ToString())),
                               new XElement("location",
                                   new XCData(Location)));
                        
                        StringWriter w = new StringWriter();
                        Xmldef.Save(w, SaveOptions.None);
                        instanceFile.Generate(w.ToString(), Path.Combine(tempPath, "info.xml"));
                        
                        responsetx.Complete();
                    }
                    scope.Complete();
                }

                Console.WriteLine(">> Fin du traitement");
                // On vérifie que tout les fichiers ont bien été écrit
                if (!File.Exists(Path.Combine(tempPath, @"info.xml")) || !File.Exists(Path.Combine(tempPath, @"requeststate.bin")))
                    throw new Exception();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                // Parano
                if (Directory.Exists(tempPath))
                    Directory.Delete(tempPath, true); // Erreur on vire tout

                noerror = false;
            }
            finally
            {
                if (rs.TypeOffRequest == RequestType.http && noerror)
                {
                    lock (this.m_Threadslock)
                    {
                        AddFileToRepositoryCache("", new AddFileinfo()
                        {
                            Uri = tempPath,
                            Typeoffile = rs.Headers[HttpResponseHeader.ContentType].Split(';')[0].ToLower() ?? "none"
                        });
                    }
                } 
                Interlocked.Decrement(ref this.m_AliveThreads);
            }
        }

        #endregion


        /// <summary>
        /// Sauvegarde de la pile d'indexation
        /// </summary>
        private void Save()
        {
            using (Stream stream = File.Open("TreatMentQueue.bin", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, this.m_DatasQueue);
                stream.Close();
            }
        }


        /// <summary>
        /// Restauration de la pile d'indexation
        /// </summary>
        private void Reload()
        {
            if (File.Exists("TreatMentQueue.bin"))
            {
                Queue TempTreatMentQueue;
                using (Stream stream = File.Open("TreatMentQueue.bin", FileMode.Open, FileAccess.Read, FileShare.None))
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    TempTreatMentQueue = (Queue)formatter.Deserialize(stream);
                    stream.Close();
                }
                if (TempTreatMentQueue.Count > 0)
                {
                    this.m_DatasQueue = new Queue(TempTreatMentQueue);
                }

                TempTreatMentQueue.Clear();
                File.Delete("TreatMentQueue.bin");
            }
        }


        public class AddFileinfo : EventArgs
        {
            public string Uri { get; set; }
            public string Typeoffile { get; set; }
        }
    }
}